"""
Model used to develop our CasseBras contest web application.
"""
from django.db import models
from django.db.models.signals import post_save, post_delete

class Category  (models.Model):
    """
    Represent a category to which a Competitor will belong to.
    """
    name            = models.CharField(blank=True, max_length=200)
        
    def rank(self):
        """
        Return the list of competitors in this category sorted by score.
        """
        return self.competitor_set.all().order_by('-score') 

    def __unicode__(self):
        """
        How to display a Category
        """
        return self.name

class Slope(models.Model):
    """
    Represent a Slope that will be climbed by Competitors.
    Every Competitors independently to his category will be able 
    to climb a Slope.
    Score of the Slope will depend of the number of Competitor who 
    reach the top.
    """
    INITIAL_SCORE   = 100
    name            = models.CharField(blank=True, max_length=100)
    initial_score   = models.IntegerField(  blank=True, null=False, 
                                            default=INITIAL_SCORE)
    score           = models.FloatField(default=INITIAL_SCORE)

    def get_score(self):
        """
        Return the score of the Slope.
        It is function of the number of Competitors who reach the top
        """
        try:
            return self.initial_score/len(self.competitor_set.all())
        except ZeroDivisionError:
            return self.initial_score
            
    def update_score(self):
        """
        Compute and save new score to score property then save to database
        """
        self.score = self.get_score()
        self.save()

    def __unicode__(self):
        """
        How to display a Slope
        """
        return self.name

class Competitor(models.Model):
    """
    Represent a Competitor/Climber of the contest.
    Each Competitor belong to one Category and only one.
    Competitor will climb slopes, their score will be the sum of the score
    of the Slopes they climb.
    """
    name            = models.CharField(blank=True, max_length=200)
    surname         = models.CharField(blank=True, max_length=200)
    climbed_slopes  = models.ManyToManyField(Slope, through='SuccessfullClimb')
    category        = models.ForeignKey(Category)
    score           = models.FloatField(default=0)
    number          = models.IntegerField(primary_key=True)
    
    def update_score(self):
        """
        Score of the Competitor.
        It's the sum of the score of the Slope the competitor climb.
        """
        self.score = sum([slope.get_score() for slope in 
                                                self.climbed_slopes.all()])
        self.save()

    def __unicode__(self):
        """
        How to display a Competitor
        """
        name = '-'.join(self.name.split(' '))
        surname = '-'.join(self.surname.split(' '))
        return '_'.join([name, surname])

class SuccessfullClimb(models.Model):
    """
    Intermediate model between Competitor and Slope used to update
    the score of Slope and Competitor linked to the slope
    every time a new association is done.
    """
    competitor  = models.ForeignKey(Competitor)
    slope       = models.ForeignKey(Slope)
        
    def __unicode__(self):
        return '<->'.join([str(self.competitor), self.slope.name])
        
    def save(self, *args, **kwargs):
        """
        We override the save methode to be sure not to store
        relations between the sames Competitor and Slope twice.
        """
        if len(SuccessfullClimb.objects.filter( competitor=self.competitor, 
                                                slope=self.slope)) == 0:

            super(SuccessfullClimb, self).save(*args, **kwargs)

class Contest   (models.Model):
    """
    Represent the Contest with all the existing Category -> All the Competitors
    """
    categories  = models.ManyToManyField(Category)

def _post_delete_callback(sender, instance, **kwargs):
    """
    Overloaded save function that will be used to update score.
    """
    if sender is SuccessfullClimb:
        # If relation has been removed then it doesn't appear
        # in SuccessfullClimb.objects anymore, we need to 
        # find competitor linked to the removed association
        # via instance object to update his score.
        instance.competitor.update_score()
    
def _post_save_callback(sender, instance, **kwargs):
    """
    Overloaded save function that will be used to update score.
    """
    if sender is SuccessfullClimb:
        instance.slope.update_score()
        map(Competitor.update_score, [sc.competitor for sc in \
            SuccessfullClimb.objects.filter(slope=instance.slope)])

post_save.connect(_post_save_callback, sender=SuccessfullClimb)
post_delete.connect(_post_delete_callback, sender=SuccessfullClimb)

